#include <kernel.h>

#define MAX_PROC_NAME           32

#define PROC_STACK_DEFAULT      10240
#define PROC_TIME_SLICE_DEFAULT 50

struct proc_event_s
{
	 int    status;
	 
	 proc_t proc;
	 struct proc_event_s *prev, *next;
	 
	 proc_event_handler_f handler;
	 void                *arg;
};

#define PROC_EVENT_OT_PENDING           1
#define PROC_EVENT_OT_PROCESSING        2
#define PROC_EVENT_PR_PENDING           3
#define PROC_EVENT_PR_PROCESSING_WEAK   4
#define PROC_EVENT_PR_PROCESSING_STRONG 5

struct proc_s
{
	 int  type;
	 char name[MAX_PROC_NAME];
	 
	 struct ekf_context_s ctx;
	 struct sched_node_s  sched_node;
	 proc_event_t         event_pool;
	 struct proc_event_s  dummy_event;

	 proc_entry_f entry;
	 void        *arg;
	 
	 int   to_yield;
	 int   status;
	 int   time_slice;
	 void *priv;
};

#define PROC_UNINIT          0
#define PROC_RUNNABLE_STRONG 1
#define PROC_RUNNABLE_WEAK   2
#define PROC_WAIT            3
#define PROC_ZOMBIE          4

#define SCHED_NODE_TO_PROC(sch) \
	 ((proc_t)((char *)(sch) - (uintptr_t)(&((proc_t)0)->sched_node)))

#define DEBUG_SLOT_COUNT 30

static volatile proc_t proc_cur;
static proc_t          proc_idle;

static volatile int    slot_count;
static volatile proc_t proc_slot[DEBUG_SLOT_COUNT];

static physaddr_t      proc_kvpt;

static void
proc_switch(proc_t proc)
{
	 proc_t old = proc_cur;
	 proc_cur = proc;

	 if (old == NULL)
	 {
		  /* Initial switch */
		  ekf_context_switch(NULL, &proc->ctx);
	 }
	 else
	 {
		  ekf_context_switch(&old->ctx, &proc->ctx);
	 }
}

extern uint32_t irq_save_level;

static void
refresh_proc_monitor()
{
	 int i, s;
	 static char k;
	 k = (k + 1) % 26;
	 // int s = "-/|\\"[k = (k + 1) % 4];
	 ekf_vga_put_sc(CRT_COLS + 0, '0' + irq_save_level);
	 ekf_vga_put_sc(CRT_COLS + 1, 'a' + k);
	 for (i = 0; i != slot_count; ++i)
	 {
	 	  switch (proc_slot[i]->status) {
	 	  case PROC_RUNNABLE_STRONG:
	 	  case PROC_RUNNABLE_WEAK:
	 		   s = (proc_slot[i] == proc_cur) ? '>' : '*';
	 		   break;
	 	  case PROC_WAIT:
	 		   s = '?';
	 		   break;
	 	  default:
	 		   s = '0' + proc_slot[i]->status;
	 	  }
	 	  ekf_vga_put_sc(CRT_COLS + i + 2, s);
	 }
}

int proc_monitor_toggle;

static void
proc_tick_event(void)
{
	 if (proc_monitor_toggle)
		  refresh_proc_monitor();
	 proc_t proc = proc_cur;
	 if (proc->time_slice == 0)
	 {
		  proc->to_yield = 1;
	 }
	 else
	 {
		  -- proc->time_slice;
	 }
}


static void
irq0_event_handler(int irq, int c)
{
	 proc_tick_event();
	 timer_tick_event(c);
}

static void
proc_public_entry_init(proc_t proc)
{
	 __asm__ __volatile__ ("sti");

	 proc->status     = PROC_RUNNABLE_WEAK;
	 proc->time_slice = PROC_TIME_SLICE_DEFAULT;

	 proc_monitor_toggle = 1;
	 intr_irq_handler_set(0, irq0_event_handler, 1);
	 ekf_intr_timer_set(100);
	 ekf_intr_irq_line_enable(0);
	 
	 proc->entry(proc->arg);
	 proc_exit();
}

void
proc_init(const char *name, int stack_size, proc_entry_f entry, void *arg)
{
	 sched_init();
	 timer_init();

	 kthread_init();
	 uthread_init();
	 
	 proc_kvpt = rcr3();
	 slot_count = 0;
	 
	 proc_cur = NULL;
	 
	 if (stack_size <= 0)
		  stack_size = PROC_STACK_DEFAULT;
	 
	 proc_t proc;
	 if ((proc = ekf_alloc(sizeof(struct proc_s))) == NULL)
		  return;
	 
	 strncpy(proc->name, name, MAX_PROC_NAME);
	 proc->name[MAX_PROC_NAME - 1] = 0;

	 proc->type  = PROC_TYPE_UNKNOWN;
	 proc->entry = entry;
	 proc->arg   = arg;
	 proc->priv  = NULL;
	 
	 proc->dummy_event.proc = proc;
	 proc->dummy_event.status = PROC_EVENT_PR_PENDING;
	 proc->dummy_event.handler = NULL;

	 uintptr_t stack_top;
	 if ((stack_top = (uintptr_t)ekf_balloc(stack_size)) == 0)
	 {
		  ekf_free(proc);
		  return;
	 }
	 
	 stack_top += stack_size;

	 ekf_context_fill(&proc->ctx, (void(*)(void *))proc_public_entry_init, proc, rcr3(), stack_top);
	 sched_init_node(&proc->sched_node, 0);
	 proc->to_yield = 0;
	 proc->status = PROC_UNINIT;

	 // The initial switch need not to be protect from IRQ
	 proc_idle = proc;
	 proc_slot[slot_count ++] = proc;
	 proc_switch(proc);
}

static void
proc_public_entry(proc_t proc)
{
	 __asm__ __volatile__ ("sti");
	 proc->entry(proc->arg);
	 proc_exit();
}

physaddr_t
proc_kvpt_get(void)
{
	 return proc_kvpt;
}

proc_t
proc_create(const char *name, uint32_t level, int stack_size, physaddr_t vpt, proc_entry_f entry, void *arg)
{
	 if (stack_size == 0)
		  stack_size = PROC_STACK_DEFAULT;
	 
	 proc_t proc;
	 if ((proc = (proc_t)ekf_alloc(sizeof(struct proc_s))) == NULL)
	 {
		  return NULL;
	 }

	 strncpy(proc->name, name, MAX_PROC_NAME);
	 proc->name[MAX_PROC_NAME - 1] = 0;

	 proc->type  = PROC_TYPE_UNKNOWN;
	 proc->entry = entry;
	 proc->arg   = arg;
	 proc->priv  = NULL;

	 proc->dummy_event.proc = proc;
	 proc->dummy_event.status = PROC_EVENT_PR_PENDING;
	 proc->dummy_event.handler = NULL;

	 uintptr_t stack_top;
	 if ((stack_top = (uintptr_t)ekf_balloc(stack_size)) == 0)
	 {
		  ekf_free(proc);
		  return NULL;
	 }
	 stack_top += stack_size;

	 ekf_context_fill(&proc->ctx, (void(*)(void *))proc_public_entry, proc, vpt, stack_top);
	 sched_init_node(&proc->sched_node, level);
	 proc->to_yield = 0;
	 proc->status = PROC_UNINIT;

	 intr_irq_save();
	 if (slot_count < DEBUG_SLOT_COUNT)
	 {
		  ekf_kprintf("proc [%s] as slot %d\n", proc->name, slot_count);
		  proc_slot[slot_count ++] = proc;
	 }
	 else ekf_kprintf("proc [%s] created but no slot for it :(\n", proc->name);
	 intr_irq_restore();

	 return proc;
}

void
proc_schedule(void)
{
	 proc_t proc = proc_cur;
	 if (proc->to_yield || sched_should_yield(&proc->sched_node))
	 {
		  proc->to_yield = 0;
		  if (
#if USE_IDLE
			   proc != proc_idle &&
#endif
			   (proc->status == PROC_RUNNABLE_WEAK ||
				proc->status == PROC_RUNNABLE_STRONG))
			   sched_insert(&proc->sched_node);

		  struct sched_node_s *s = sched_pick();
		  proc_t nproc;
		  
		  if (s != NULL)
		  {
			   sched_remove(s);
			   nproc = SCHED_NODE_TO_PROC(s);
		  }
		  else
		  {
#if USE_IDLE
			   nproc = proc_idle;
#else
			   nproc = proc;
#endif
		  }

		  nproc->time_slice = PROC_TIME_SLICE_DEFAULT;
		  if (nproc != proc)
		  {
			   // ekf_kprintf("schedule to %08x\n", nproc);
			   proc_switch(nproc);
		  }
		  else
		  {
			   // ekf_kprintf("schedule to self =_=\n");
		  }
	 }
}

void
proc_exit(void)
{
	 proc_t proc = proc_cur;

	 intr_irq_save();
	 
	 proc->status = PROC_ZOMBIE;
	 proc->to_yield = 1;
	 
	 intr_irq_restore_schedule();
}

proc_t
proc_self(void)
{
	 return proc_cur;
}

void *
proc_priv_get(void)
{
	 return proc_cur->priv;
}

void
proc_priv_set(void *priv)
{
	 proc_cur->priv = priv;
}

void
proc_attach(proc_t proc)
{
	 intr_irq_save();

	 proc->status = PROC_RUNNABLE_WEAK;
	 sched_insert(&proc->sched_node);

	 intr_irq_restore();
}

void
proc_yield(void)
{
	 intr_irq_save();
	 proc_cur->to_yield = 1;
	 intr_irq_restore_schedule();
}

void
proc_do_idle(void)
{
	 while (1)
	 {
		  proc_yield();		  
	 }
}

void
proc_to_wait(void)
{
	 proc_t proc = proc_cur;
	 proc->status   = PROC_WAIT;
	 proc->to_yield = 1;
}

int
proc_self_type_get(void)
{
	 return proc_cur->type;
}

void
proc_self_type_set(int type)
{
	 proc_cur->type = type;
}

void
proc_legacy_wait_pretend(void)
{
	 intr_irq_save();

	 if (proc_cur->status == PROC_RUNNABLE_STRONG)
		  proc_cur->status = PROC_RUNNABLE_WEAK;

	 intr_irq_restore();
}

void
proc_legacy_wait_try(void)
{
	 intr_irq_save();
	 
	 switch (proc_cur->status)
	 {
	 case PROC_RUNNABLE_STRONG:
		  proc_cur->status = PROC_RUNNABLE_WEAK;
		  break;
	 case PROC_RUNNABLE_WEAK:
		  proc_cur->status = PROC_WAIT;
		  proc_cur->to_yield = 1;
		  break;
	 }

	 if (proc_cur->to_yield)
		  intr_irq_restore_schedule();
	 else intr_irq_restore();
}

void
proc_legacy_wait(void)
{
	 intr_irq_save();

	 proc_cur->status   = PROC_WAIT;
	 proc_cur->to_yield = 1;

	 intr_irq_restore_schedule();
}

void
proc_legacy_notify(proc_t proc)
{
	 intr_irq_save();
	 
	 int i;
	 
	 switch (proc->status)
	 {
	 case PROC_WAIT:
		  proc->status = PROC_RUNNABLE_WEAK;
		  sched_insert(&proc->sched_node);

		  break;
	 case PROC_RUNNABLE_WEAK:
		  proc->status = PROC_RUNNABLE_STRONG;
		  break;
	 }

	 intr_irq_restore();
}


/* ============================================================ */

static proc_event_t global_event_pool;

proc_event_t
proc_self_dummy_event(void)
{
	 return &proc_cur->dummy_event;
}

proc_event_t
proc_event_local_create(int persistent, proc_event_handler_f handler, void *arg)
{
	 proc_event_t e = (proc_event_t)ekf_alloc(sizeof(struct proc_event_s));
	 
	 if (e != NULL)
	 {
		  e->proc = proc_cur;

		  if (persistent)
			   e->status = PROC_EVENT_PR_PENDING;
		  else e->status = PROC_EVENT_OT_PENDING;
		  
		  e->handler = handler;
		  e->arg = arg;
	 }

	 return e;
}

proc_event_t
proc_event_create(int persistent, proc_t proc, proc_event_handler_f handler, void *arg)
{
	 proc_event_t e = (proc_event_t)ekf_alloc(sizeof(struct proc_event_s));
	 
	 if (e != NULL)
	 {
		  e->proc = proc;

		  if (persistent)
			   e->status = PROC_EVENT_PR_PENDING;
		  else e->status = PROC_EVENT_OT_PENDING;
		  
		  e->handler = handler;
		  e->arg = arg;
	 }

	 return e;
}

static void
proc_event_enqueue(proc_event_t event)
{
	 proc_t p = event->proc;
	 if (p != NULL)
	 {
		  if (p->event_pool == NULL)
		  {
			   p->event_pool =
					event->prev =
					event->next = event;
		  }
		  else
		  {
			   event->next = p->event_pool;
			   event->prev = event->next->prev;

			   event->next->prev = event;
			   event->prev->next = event;
		  }
	 }
	 else
	 {
		  if (global_event_pool == NULL)
		  {
			   global_event_pool =
					event->prev =
					event->next = event;
		  }
		  else
		  {
			   event->next = global_event_pool;
			   event->prev = event->next->prev;

			   event->next->prev = event;
			   event->prev->next = event;
		  }
	 }
}

static proc_event_t
proc_event_dequeue(void)
{
	 proc_event_t e;
	 if (global_event_pool != NULL)
	 {
		  e = global_event_pool;
		  if (e->next == e)
		  {
			   global_event_pool = NULL;
		  }
		  else
		  {
			   global_event_pool = e->next;
			   e->next->prev = e->prev;
			   e->prev->next = e->next;
		  }
	 }
	 else if (proc_cur->event_pool != NULL)
	 {
		  e = proc_cur->event_pool;
		  if (e->next == e)
		  {
			   proc_cur->event_pool = NULL;
		  }
		  else
		  {
			   proc_cur->event_pool = e->next;
			   e->next->prev = e->prev;
			   e->prev->next = e->next;
		  }
	 }
	 else return NULL;
	 return e;
}

int
proc_event_activate(proc_event_t event)
{
	 int r = 0;
	 proc_t proc;
	 intr_irq_save();

	 if (event->handler != NULL)
	 {
		  switch (event->status)
		  {
		  case PROC_EVENT_OT_PENDING:
			   event->status = PROC_EVENT_OT_PROCESSING;
			   proc_event_enqueue(event);
			   break;
			   
		  case PROC_EVENT_PR_PENDING:
			   event->status = PROC_EVENT_PR_PROCESSING_WEAK;
			   proc_event_enqueue(event);
			   break;
			   
		  case PROC_EVENT_PR_PROCESSING_WEAK:
			   event->status = PROC_EVENT_PR_PROCESSING_STRONG;
		  case PROC_EVENT_PR_PROCESSING_STRONG:
			   break;
		  default:
			   r = -1;
			   break;
		  }
	 }

	 if (r == 0 && (proc = event->proc) != NULL)
	 {
		  switch (proc->status)
		  {
		  case PROC_WAIT:
			   proc->status   = PROC_RUNNABLE_WEAK;
			   proc->to_yield = 0;
			   sched_insert(&proc->sched_node);
			   break;
			   
		  case PROC_RUNNABLE_WEAK:
			   proc->status = PROC_RUNNABLE_STRONG;
			   break;
		  }
	 }
	 
	 intr_irq_restore();
}

void
proc_event_process_irq_save(void)
{
	 proc_t proc = proc_cur;
	 intr_irq_save();
	 while (1)
	 {
		  proc_event_t e = proc_event_dequeue();
		  if (e == NULL)
			   return;

		  intr_irq_restore();
		  
		  if (e->handler)
			   e->handler(e->arg);
		  else
		  {
			   /* THIS SHOULD NOT HAPPEN */
		  }

		  intr_irq_save();

		  if (e->status == PROC_EVENT_PR_PROCESSING_STRONG)
		  {
			   e->status = PROC_EVENT_PR_PROCESSING_WEAK;
			   proc_event_enqueue(e);
		  }
		  else if (e->status == PROC_EVENT_OT_PROCESSING)
			   ekf_free(e);
		  else e->status = PROC_EVENT_PR_PENDING;
	 }
}

void
proc_destroy(proc_t proc)
{
	 ekf_kprintf("PROC_DESTROY: NOT IMPLEMENTED\n");
}
